
#include <map>
#include "TaskSyncFile.h"
#include "SyncFileModule.h"
#include "../client-protocol/ConnectionFactory.h"
#include "../client-protocol/ConnectionControl.h"
#include "../client-common/Config.h"

using namespace std;
using namespace Engine;

namespace Client {
namespace File {

bool CTaskSyncFile::GetFileList(int oldver, vector<SyncFileRef>& sfs, boost::shared_ptr<CConnection> c)
{
	boost::shared_ptr<CRequest> request(new CRequest(string(COMMAND_GET)));
	request->AddParameter(string("target"), string("filelist"));
	request->AddParameter(string("oldver"), string(boost::lexical_cast<string>(oldver)));
	if(IsFullySync()) request->AddParameter(string("devices"), string("all"));
	ResponseRef response = c->SendRequest(request);
	if(!response || response->GetStatus() != RESPONSE_STATUS_OK) {
		Engine::DebugStream << "CTaskSyncFile::Exec() - " << "Get file list failed\n";
		return false;
	}

	string js;
	response->GetContentAsString(js);

	DebugStream << "CTaskSyncFile::Exec - " << js << '\n';

	TableRef tf = CSyncFile::GetSerializedTable(js);
	if(!tf) {
		Engine::DebugStream << "CTaskSyncFile::Exec() - " << "Invalid file list\n";
		return true;
	}

	boost::shared_array<RecordRef> recs = tf->NextRecords(tf->GetCount());
	int count = tf->GetCurrentRecordsCount();

	map<string, VersionTagRef> oldvtags;
	map<string, VersionTagRef> vtags;

	Client::Common::CConfig& conf = Client::Common::CConfig::GetInstance();
	for(int i = 0; i < count; i++) {
		SyncFileRef sf = boost::dynamic_pointer_cast<CSyncFile, CRecord>(recs[i]);
		VersionTagRef oldvt;
		map<string, VersionTagRef>::iterator iter = oldvtags.find(sf->mLastModifyDevice);
		if(iter == oldvtags.end()) {
			oldvt = CVersionTag::QueryDeviceTag(sf->mLastModifyDevice, string(VERSION_TAG_FILE));
			if(!oldvt) {
				oldvt = VersionTagRef(new CVersionTag());
				oldvt->mTagName = VERSION_TAG_FILE;
				if(sf->mLastModifyDevice != conf.mDeviceId) {
					oldvt->mDevice = sf->mLastModifyDevice;
				}
			}
			oldvtags[sf->mLastModifyDevice] = oldvt;
		}
		else {
			oldvt = iter->second;
		}
		
		if(oldvt->mHeadVersion < sf->mVersion) {
			if(sf->mType == FILE_TYPE_FILE && sf->mStatus == FILE_STATUS_NORMAL) {
				sf->mStatus = FILE_STATUS_LOST;
				sfs.push_back(sf);
			}

			CSyncFile::AddSyncFile(sf);
		}

		VersionTagRef vt;
		iter = vtags.find(sf->mLastModifyDevice);
		if(iter == vtags.end()) {
			vt = VersionTagRef(new CVersionTag());
			vt->mTagName = VERSION_TAG_FILE;
			if(sf->mLastModifyDevice != conf.mDeviceId) {
				vt->mDevice = sf->mLastModifyDevice;
			}
			vt->mHeadVersion = sf->mVersion;
			vtags[sf->mLastModifyDevice] = vt;
		}
		else {
			vt = iter->second;
			if(vt->mHeadVersion < sf->mVersion) vt->mHeadVersion = sf->mVersion;
		}
	}

	for(map<string, VersionTagRef>::iterator iter = vtags.begin(); iter != vtags.end(); iter++) {
		CVersionTag::AddDeviceTag(iter->second);
	}

	return true;
}

bool CTaskSyncFile::SyncFile(SyncFileRef sf, ConnectionRef c) {

	boost::filesystem::path folder = boost::filesystem::current_path() / sf->mOrigDevice;
	if(!boost::filesystem::exists(folder)) boost::filesystem::create_directories(folder);

	boost::shared_ptr<CRequest> request(new CRequest(string(COMMAND_GET)));
	request->AddParameter(string("target"), string("file"));
	request->AddParameter(string("id"), sf->mId);
	ResponseRef response = c->SendRequest(request);
	if(!response || response->GetStatus() != RESPONSE_STATUS_OK) {
		Engine::DebugStream << "CTaskSyncFile::SyncFile() - " << "Download file failed\n";
		return false;
	}

	int dot_pos = sf->mName.find_last_of('.');
	sf->mStorePath = (folder / sf->mId).string();
	if(dot_pos > 0) sf->mStorePath.append(sf->mName.substr(dot_pos));

	Engine::DebugStream << "CTaskSyncFile::SyncFile() - save file: " << sf->mStorePath << '\n';
	if(!response->SaveContent(sf->mStorePath)) {
		Engine::DebugStream << "CTaskSyncFile::SyncFile() - " << "Save file failed\n";
		return false;
	}

	try {
		boost::filesystem::path file = sf->mStorePath;
		time_t tm = boost::filesystem::last_write_time(file);
		sf->mStoreTag = boost::lexical_cast<string>(tm);
	}
	catch(std::exception& e) {
		Engine::DebugStream << "CTaskSyncFile::SyncFile() - " << e.what() << '\n';
		return false;
	}

	sf->mStatus = FILE_STATUS_NORMAL;
	return CSyncFile::AddSyncFile(sf);
}

int CTaskSyncFile::GetOldVersion()
{
	Client::Common::CConfig& conf = Client::Common::CConfig::GetInstance();
	VersionTagRef oldvt;
	if(conf.mDeviceId == mDevice) oldvt = CVersionTag::QueryLocalTag(string(VERSION_TAG_FILE));
	else oldvt = CVersionTag::QueryDeviceTag(mDevice, string(VERSION_TAG_FILE));

	if(oldvt) return oldvt->mHeadVersion;
	else return 0;
}

bool CTaskSyncFile::Exec(boost::shared_ptr<CConnection> conn) {

	boost::shared_ptr<CConnectionControl> control_conn;
	ConnectionRef c;

	if(conn->IsControlConnection()) {
		control_conn = boost::dynamic_pointer_cast<CConnectionControl, CConnection>(conn);
	
		c = CConnectionFactory::CreateByPeer(mDevice, control_conn, false);
		if(!c) {
			Engine::DebugStream << "CTaskSyncFile::Exec() - " << "Create connection failed\n";
			return false;
		}

		try {
			c->Setup();
		}
		catch(std::exception&) {
			return false;
		}
	}
	else {
		c = conn;
	}

	int oldver = 0;
	if(!IsFullySync()) oldver = GetOldVersion();
	
	// sort by version
	vector<SyncFileRef> sfs;
	if(!GetFileList(oldver, sfs, c)) return false;

	for(vector<SyncFileRef>::iterator i = sfs.begin(); i != sfs.end(); i++) {
		SyncFile(*i, c);
	}

	if(mVersionTag && control_conn) control_conn->AddCheckedVersionTag(mVersionTag);

	return true;
}

class CTaskSyncFileCreator : public CTaskCreator {

public :

	virtual TaskRef Create(VersionTagRef vt) {
		return TaskRef(new CTaskSyncFile(vt, false));
	}

	virtual TaskRef CreateFullyTask(string& dev) {
		return TaskRef(new CTaskSyncFile(dev, true));
	}
};

static CTaskSyncFileCreator sTaskSyncFileCreator;

void CSyncFileModule::InitTaskCreator()
{
	CTaskFactory::RegisterCreator(string("file"), &sTaskSyncFileCreator);
}

} // Client
} // File